﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using LINQPad.OpenAccess.Helpers;
using Telerik.OpenAccess;
using Telerik.OpenAccess.SPI;
using Telerik.OpenAccess.SPI.dataobjects;

namespace LINQPad.OpenAccess.Services
{
    /// <summary>
    /// <see cref="IDataContextCustomMemberProviderFactory"/>.
    /// </summary>
    public class DataContextCustomMemberProviderFactory : IDataContextCustomMemberProviderFactory
    {
        #region Private

        #region - Vars

        [DebuggerBrowsable( DebuggerBrowsableState.Never )]
        private IDictionary<Type, PropertyDescriptorCollection> _typeProperties = new Dictionary<Type, PropertyDescriptorCollection>( );

        #endregion

        #region - Functions

        /// <summary>
        /// Gets the properties.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns>
        /// The properties.
        /// </returns>
        private PropertyDescriptorCollection GetPropertiesForType( Type entityType )
        {
            Guard.ArgumentNotNull( entityType, "entityType" );
            PropertyDescriptorCollection properties;
            if ( !this._typeProperties.TryGetValue( entityType, out properties ) )
                this._typeProperties[entityType] = properties = TypeDescriptor.GetProperties( entityType );
            return properties;
        }

        #endregion

        #endregion

        #region Protected

        #region - Functions

        /// <summary>
        /// Tries to get the custom member provider for the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="memberProvider">The member provider.</param>
        /// <returns>
        ///   <c>true</c> on success; otherwise, <c>false</c>.
        /// </returns>
        public bool TryGetCustomMemberProviderFor( PersistenceCapable entity, out ICustomMemberProvider memberProvider )
        {
            IList<string> notLoadedMembers;
            if ( this.TryGetNotLoadedNavigationPropertyNames( entity, out notLoadedMembers ) )
            {

                ICustomTypeDescriptor customTypeDescriptor;
                this.TryGetCustomTypeDescriptorFromEntity( entity, out customTypeDescriptor );

                var propertyCollection = customTypeDescriptor == null ? this.GetPropertiesForType( entity.GetType( ) ) : customTypeDescriptor.GetProperties( );
                Func<object, PropertyDescriptor, object> valueSelector = null;
                if ( notLoadedMembers != null || notLoadedMembers.Count > 0 )
                    valueSelector = ( e, p ) => notLoadedMembers.Contains( p.DisplayName ) ? null : p.GetValue( e );

                memberProvider = new Internal.ConstantPropertyMemberProvider( entity, propertyCollection, valueSelector );
                return true;
            }
            memberProvider = null;
            return false;
        }
        /// <summary>
        /// Tries to the get the custom type descriptor from the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="customTypeDescriptor">The custom type descriptor.</param>
        /// <returns>
        ///   <c>true</c> on success; otherwise, <c>false</c>.
        /// </returns>
        protected bool TryGetCustomTypeDescriptorFromEntity( PersistenceCapable entity, out ICustomTypeDescriptor customTypeDescriptor )
        {
            IExtendedObjectScope scope;
            if ( this.TryGetScopeFromEntity<IExtendedObjectScope>( entity, out scope )
                    && scope.ExtendedPersistenceManager != null )
            {
                customTypeDescriptor = scope.ExtendedPersistenceManager
                                            .UserVisibleMetaData
                                            .GetExtendedTypeDescriptor( entity );
                return true;
            }
            customTypeDescriptor = null;
            return false;
        }
        /// <summary>
        /// Tries to get the names of the navigation properties that have not yet been loaded from the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="notLoadedNavigationProperties">The names of the not yet loaded navigation properties.</param>
        /// <returns>
        ///   <c>true</c> on success; otherwise, <c>false</c>.
        /// </returns>
        protected bool TryGetNotLoadedNavigationPropertyNames( PersistenceCapable entity, out IList<string> notLoadedNavigationProperties )
        {
            IObjectScope scope;
            IEnumerable<string> innerResult;
            if ( this.TryGetScopeFromEntity( entity, out scope )
                    && null != ( innerResult = scope.GetMemberNames( entity, ObjectState.NotLoaded ) ) )
            {

                notLoadedNavigationProperties = innerResult as IList<string>;
                if ( notLoadedNavigationProperties == null )
                    notLoadedNavigationProperties = innerResult.ToList( );
                return true;

            }
            notLoadedNavigationProperties = null;
            return false;
        }
        /// <summary>
        /// Tries to get the scope from the specified entity.
        /// </summary>
        /// <typeparam name="TScope">The type of the scope.</typeparam>
        /// <param name="entity">The entity.</param>
        /// <param name="scope">The scope.</param>
        /// <returns>
        ///   <c>true</c> on success; otherwise, <c>false</c>.
        /// </returns>
        protected bool TryGetScopeFromEntity<TScope>( PersistenceCapable entity, out TScope scope )
            where TScope : class, IObjectScope
        {
            IPersistenceManager mgr;
            if ( entity == null
                    || null == ( mgr = entity.OpenAccessEnhancedGetPersistenceManager( ) )
                    || null == ( scope = mgr.getUserObject( ) as TScope ) )
            {
                scope = null;
                return false;
            };
            return true;
        }

        #endregion

        #endregion

        #region IDataContextCustomMemberProviderFactory Members

        /// <summary>
        /// Gets the custom display member provider for the specified object.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns>
        /// The custom display member provider or <c>null</c>.
        /// </returns>
        public ICustomMemberProvider GetCustomDisplayMemberProvider( object obj )
        {
            var persistable = obj as PersistenceCapable;
            if ( persistable == null )
                return null;
            ICustomMemberProvider memberProvider;
            if ( this.TryGetCustomMemberProviderFor( persistable, out memberProvider ) )
                return memberProvider;
            
            return null;
        }

        #endregion
    }
}
